package com.jse.json;

import java.util.Iterator;

/*
Public Domain.
*/

/**
 * This provides static methods to convert an XML text into a JSONArray or
 * JSONObject, and to covert a JSONArray or JSONObject into an XML text using
 * the JsonML transform.
 *
 * @author JSON.org
 * @version 2016-01-30
 */
public class XML {

	public static final Character AMP = '&';
	public static final Character APOS = '\'';
	public static final Character BANG = '!';
	public static final Character EQ = '=';
	public static final Character GT = '>';
	public static final Character LT = '<';
	public static final Character QUEST = '?';
	public static final Character QUOT = '"';
	public static final Character SLASH = '/';
	public static final ParserConfiguration KEEP_STRINGS = new ParserConfiguration().withKeepStrings(true);
	public static final ParserConfiguration ORIGINAL = new ParserConfiguration();

	/**
	 * Parse XML values and store them in a JSONArray.
	 * 
	 * @param x           The XMLTokener containing the source string.
	 * @param arrayForm   true if array form, false if object form.
	 * @param ja          The JSONArray that is containing the current tag or null
	 *                    if we are at the outermost level.
	 * @param keepStrings Don't type-convert text nodes and attribute values
	 * @return A JSONArray if the value is the outermost tag, otherwise null.
	 * @throws RuntimeException if a parsing error occurs
	 */
	private static Object parse(XMLTokener x, boolean arrayForm, JsonArray ja, boolean keepStrings,
			int currentNestingDepth) throws RuntimeException {
		return parse(x, arrayForm, ja, keepStrings ? KEEP_STRINGS : ORIGINAL, currentNestingDepth);
	}

	/**
	 * Parse XML values and store them in a JSONArray.
	 * 
	 * @param x         The XMLTokener containing the source string.
	 * @param arrayForm true if array form, false if object form.
	 * @param ja        The JSONArray that is containing the current tag or null if
	 *                  we are at the outermost level.
	 * @param config    The parser configuration: JSONMLParserConfiguration.ORIGINAL
	 *                  is the default behaviour;
	 *                  JSONMLParserConfiguration.KEEP_STRINGS means Don't
	 *                  type-convert text nodes and attribute values.
	 * @return A JSONArray if the value is the outermost tag, otherwise null.
	 * @throws RuntimeException if a parsing error occurs
	 */
	private static Object parse(XMLTokener x, boolean arrayForm, JsonArray ja, ParserConfiguration config,
			int currentNestingDepth) throws RuntimeException {
		String attribute;
		char c;
		String closeTag = null;
		int i;
		JsonArray newja = null;
		JsonObject newjo = null;
		Object token;
		String tagName = null;
		while (true) {
			if (!x.more()) {
				throw x.syntaxError("Bad XML");
			}
			token = x.nextContent();
			if (token == LT) {
				token = x.nextToken();
				if (token instanceof Character) {
					if (token == SLASH) {
						token = x.nextToken();
						if (!(token instanceof String)) {
							throw new RuntimeException("Expected a closing name instead of '" + token + "'.");
						}
						if (x.nextToken() != GT) {
							throw x.syntaxError("Misshaped close tag");
						}
						return token;
					} else if (token == BANG) {
						c = x.next();
						if (c == '-') {
							if (x.next() == '-') {
								x.skipPast("-->");
							} else {
								x.back();
							}
						} else if (c == '[') {
							token = x.nextToken();
							if (token.equals("CDATA") && x.next() == '[') {
								if (ja != null) {
									ja.add(x.nextCDATA());
								}
							} else {
								throw x.syntaxError("Expected 'CDATA['");
							}
						} else {
							i = 1;
							do {
								token = x.nextMeta();
								if (token == null) {
									throw x.syntaxError("Missing '>' after '<!'.");
								} else if (token == LT) {
									i += 1;
								} else if (token == GT) {
									i -= 1;
								}
							} while (i > 0);
						}
					} else if (token == QUEST) {
						x.skipPast("?>");
					} else {
						throw x.syntaxError("Misshaped tag");
					}
				} else {
					if (!(token instanceof String)) {
						throw x.syntaxError("Bad tagName '" + token + "'.");
					}
					tagName = (String) token;
					newja = new JsonArray();
					newjo = new JsonObject();
					if (arrayForm) {
						newja.add(tagName);
						if (ja != null) {
							ja.add(newja);
						}
					} else {
						newjo.put("tagName", tagName);
						if (ja != null) {
							ja.add(newjo);
						}
					}
					token = null;
					for (;;) {
						if (token == null) {
							token = x.nextToken();
						}
						if (token == null) {
							throw x.syntaxError("Misshaped tag");
						}
						if (!(token instanceof String)) {
							break;
						}
						attribute = (String) token;
						if (!arrayForm && ("tagName".equals(attribute) || "childNode".equals(attribute))) {
							throw x.syntaxError("Reserved attribute.");
						}
						token = x.nextToken();
						if (token == EQ) {
							token = x.nextToken();
							if (!(token instanceof String)) {
								throw x.syntaxError("Missing value");
							}
							newjo.accumulate(attribute,
									config.isKeepStrings() ? ((String) token) : stringToValue((String) token));
							token = null;
						} else {
							newjo.accumulate(attribute, "");
						}
					}
					if (arrayForm && newjo.size() > 0) {
						newja.add(newjo);
					}
					if (token == SLASH) {
						if (x.nextToken() != GT) {
							throw x.syntaxError("Misshaped tag");
						}
						if (ja == null) {
							if (arrayForm) {
								return newja;
							}
							return newjo;
						}
					} else {
						if (token != GT) {
							throw x.syntaxError("Misshaped tag");
						}

						if (currentNestingDepth == config.getMaxNestingDepth()) {
							throw x.syntaxError("Maximum nesting depth of " + config.getMaxNestingDepth() + " reached");
						}

						closeTag = (String) parse(x, arrayForm, newja, config, currentNestingDepth + 1);
						if (closeTag != null) {
							if (!closeTag.equals(tagName)) {
								throw x.syntaxError("Mismatched '" + tagName + "' and '" + closeTag + "'");
							}
							tagName = null;
							if (!arrayForm && newja.length() > 0) {
								newjo.put("childNodes", newja);
							}
							if (ja == null) {
								if (arrayForm) {
									return newja;
								}
								return newjo;
							}
						}
					}
				}
			} else {
				if (ja != null) {
					ja.add(token instanceof String
							? (config.isKeepStrings() ? unescape((String) token) : stringToValue((String) token))
							: token);
				}
			}
		}
	}

	/**
	 * Convert a well-formed (but not necessarily valid) XML string into a JSONArray
	 * using the JsonML transform. Each XML tag is represented as a JSONArray in
	 * which the first element is the tag name. If the tag has attributes, then the
	 * second element will be JSONObject containing the name/value pairs. If the tag
	 * contains children, then strings and JSONArrays will represent the child tags.
	 * Comments, prologs, DTDs, and
	 * 
	 * <pre>{@code &lt;[ [ ]]>}</pre>
	 * 
	 * are ignored.
	 * 
	 * @param string The source string.
	 * @return A JSONArray containing the structured data from the XML string.
	 * @throws RuntimeException Thrown on error converting to a JSONArray
	 */
	public static JsonArray toJSONArray(String string) throws RuntimeException {
		return (JsonArray) parse(new XMLTokener(string), true, null, ORIGINAL, 0);
	}

	/**
	 * Convert a well-formed (but not necessarily valid) XML string into a JSONArray
	 * using the JsonML transform. Each XML tag is represented as a JSONArray in
	 * which the first element is the tag name. If the tag has attributes, then the
	 * second element will be JSONObject containing the name/value pairs. If the tag
	 * contains children, then strings and JSONArrays will represent the child tags.
	 * As opposed to toJSONArray this method does not attempt to convert any text
	 * node or attribute value to any type but just leaves it as a string. Comments,
	 * prologs, DTDs, and
	 * 
	 * <pre>{@code &lt;[ [ ]]>}</pre>
	 * 
	 * are ignored.
	 * 
	 * @param string      The source string.
	 * @param keepStrings If true, then values will not be coerced into boolean or
	 *                    numeric values and will instead be left as strings
	 * @return A JSONArray containing the structured data from the XML string.
	 * @throws RuntimeException Thrown on error converting to a JSONArray
	 */
	public static JsonArray toJSONArray(String string, boolean keepStrings) throws RuntimeException {
		return (JsonArray) parse(new XMLTokener(string), true, null, keepStrings, 0);
	}

	/**
	 * Convert a well-formed (but not necessarily valid) XML string into a JSONArray
	 * using the JsonML transform. Each XML tag is represented as a JSONArray in
	 * which the first element is the tag name. If the tag has attributes, then the
	 * second element will be JSONObject containing the name/value pairs. If the tag
	 * contains children, then strings and JSONArrays will represent the child tags.
	 * As opposed to toJSONArray this method does not attempt to convert any text
	 * node or attribute value to any type but just leaves it as a string. Comments,
	 * prologs, DTDs, and
	 * 
	 * <pre>{@code &lt;[ [ ]]>}</pre>
	 * 
	 * are ignored.
	 * 
	 * @param string The source string.
	 * @param config The parser configuration: JSONMLParserConfiguration.ORIGINAL is
	 *               the default behaviour; JSONMLParserConfiguration.KEEP_STRINGS
	 *               means values will not be coerced into boolean or numeric values
	 *               and will instead be left as strings
	 * @return A JSONArray containing the structured data from the XML string.
	 * @throws RuntimeException Thrown on error converting to a JSONArray
	 */
	public static JsonArray toJSONArray(String string, ParserConfiguration config) throws RuntimeException {
		return (JsonArray) parse(new XMLTokener(string), true, null, config, 0);
	}

	/**
	 * Convert a well-formed (but not necessarily valid) XML string into a JSONArray
	 * using the JsonML transform. Each XML tag is represented as a JSONArray in
	 * which the first element is the tag name. If the tag has attributes, then the
	 * second element will be JSONObject containing the name/value pairs. If the tag
	 * contains children, then strings and JSONArrays will represent the child
	 * content and tags. As opposed to toJSONArray this method does not attempt to
	 * convert any text node or attribute value to any type but just leaves it as a
	 * string. Comments, prologs, DTDs, and
	 * 
	 * <pre>{@code &lt;[ [ ]]>}</pre>
	 * 
	 * are ignored.
	 * 
	 * @param x      An XMLTokener.
	 * @param config The parser configuration: JSONMLParserConfiguration.ORIGINAL is
	 *               the default behaviour; JSONMLParserConfiguration.KEEP_STRINGS
	 *               means values will not be coerced into boolean or numeric values
	 *               and will instead be left as strings
	 * @return A JSONArray containing the structured data from the XML string.
	 * @throws RuntimeException Thrown on error converting to a JSONArray
	 */
	public static JsonArray toJSONArray(XMLTokener x, ParserConfiguration config) throws RuntimeException {
		return (JsonArray) parse(x, true, null, config, 0);
	}

	/**
	 * Convert a well-formed (but not necessarily valid) XML string into a JSONArray
	 * using the JsonML transform. Each XML tag is represented as a JSONArray in
	 * which the first element is the tag name. If the tag has attributes, then the
	 * second element will be JSONObject containing the name/value pairs. If the tag
	 * contains children, then strings and JSONArrays will represent the child
	 * content and tags. As opposed to toJSONArray this method does not attempt to
	 * convert any text node or attribute value to any type but just leaves it as a
	 * string. Comments, prologs, DTDs, and
	 * 
	 * <pre>{@code &lt;[ [ ]]>}</pre>
	 * 
	 * are ignored.
	 * 
	 * @param x           An XMLTokener.
	 * @param keepStrings If true, then values will not be coerced into boolean or
	 *                    numeric values and will instead be left as strings
	 * @return A JSONArray containing the structured data from the XML string.
	 * @throws RuntimeException Thrown on error converting to a JSONArray
	 */
	public static JsonArray toJSONArray(XMLTokener x, boolean keepStrings) throws RuntimeException {
		return (JsonArray) parse(x, true, null, keepStrings, 0);
	}

	/**
	 * Convert a well-formed (but not necessarily valid) XML string into a JSONArray
	 * using the JsonML transform. Each XML tag is represented as a JSONArray in
	 * which the first element is the tag name. If the tag has attributes, then the
	 * second element will be JSONObject containing the name/value pairs. If the tag
	 * contains children, then strings and JSONArrays will represent the child
	 * content and tags. Comments, prologs, DTDs, and
	 * 
	 * <pre>{@code &lt;[ [ ]]>}</pre>
	 * 
	 * are ignored.
	 * 
	 * @param x An XMLTokener.
	 * @return A JSONArray containing the structured data from the XML string.
	 * @throws RuntimeException Thrown on error converting to a JSONArray
	 */
	public static JsonArray toJSONArray(XMLTokener x) throws RuntimeException {
		return (JsonArray) parse(x, true, null, false, 0);
	}

	/**
	 * Convert a well-formed (but not necessarily valid) XML string into a
	 * JSONObject using the JsonML transform. Each XML tag is represented as a
	 * JSONObject with a "tagName" property. If the tag has attributes, then the
	 * attributes will be in the JSONObject as properties. If the tag contains
	 * children, the object will have a "childNodes" property which will be an array
	 * of strings and JsonML JSONObjects.
	 * 
	 * Comments, prologs, DTDs, and
	 * 
	 * <pre>{@code &lt;[ [ ]]>}</pre>
	 * 
	 * are ignored.
	 * 
	 * @param string The XML source text.
	 * @return A JSONObject containing the structured data from the XML string.
	 * @throws RuntimeException Thrown on error converting to a JSONObject
	 */
	public static JsonObject toJSONObject(String string) throws RuntimeException {
		return (JsonObject) parse(new XMLTokener(string), false, null, false, 0);
	}

	/**
	 * Convert a well-formed (but not necessarily valid) XML string into a
	 * JSONObject using the JsonML transform. Each XML tag is represented as a
	 * JSONObject with a "tagName" property. If the tag has attributes, then the
	 * attributes will be in the JSONObject as properties. If the tag contains
	 * children, the object will have a "childNodes" property which will be an array
	 * of strings and JsonML JSONObjects.
	 * 
	 * Comments, prologs, DTDs, and
	 * 
	 * <pre>{@code &lt;[ [ ]]>}</pre>
	 * 
	 * are ignored.
	 * 
	 * @param string      The XML source text.
	 * @param keepStrings If true, then values will not be coerced into boolean or
	 *                    numeric values and will instead be left as strings
	 * @return A JSONObject containing the structured data from the XML string.
	 * @throws RuntimeException Thrown on error converting to a JSONObject
	 */
	public static JsonObject toJSONObject(String string, boolean keepStrings) throws RuntimeException {
		return (JsonObject) parse(new XMLTokener(string), false, null, keepStrings, 0);
	}

	/**
	 * Convert a well-formed (but not necessarily valid) XML string into a
	 * JSONObject using the JsonML transform. Each XML tag is represented as a
	 * JSONObject with a "tagName" property. If the tag has attributes, then the
	 * attributes will be in the JSONObject as properties. If the tag contains
	 * children, the object will have a "childNodes" property which will be an array
	 * of strings and JsonML JSONObjects.
	 * 
	 * Comments, prologs, DTDs, and
	 * 
	 * <pre>{@code &lt;[ [ ]]>}</pre>
	 * 
	 * are ignored.
	 * 
	 * @param string The XML source text.
	 * @param config The parser configuration: JSONMLParserConfiguration.ORIGINAL is
	 *               the default behaviour; JSONMLParserConfiguration.KEEP_STRINGS
	 *               means values will not be coerced into boolean or numeric values
	 *               and will instead be left as strings
	 * @return A JSONObject containing the structured data from the XML string.
	 * @throws RuntimeException Thrown on error converting to a JSONObject
	 */
	public static JsonObject toJSONObject(String string, ParserConfiguration config) throws RuntimeException {
		return (JsonObject) parse(new XMLTokener(string), false, null, config, 0);
	}

	/**
	 * Convert a well-formed (but not necessarily valid) XML string into a
	 * JSONObject using the JsonML transform. Each XML tag is represented as a
	 * JSONObject with a "tagName" property. If the tag has attributes, then the
	 * attributes will be in the JSONObject as properties. If the tag contains
	 * children, the object will have a "childNodes" property which will be an array
	 * of strings and JsonML JSONObjects.
	 * 
	 * Comments, prologs, DTDs, and
	 * 
	 * <pre>{@code &lt;[ [ ]]>}</pre>
	 * 
	 * are ignored.
	 * 
	 * @param x An XMLTokener of the XML source text.
	 * @return A JSONObject containing the structured data from the XML string.
	 * @throws RuntimeException Thrown on error converting to a JSONObject
	 */
	public static JsonObject toJSONObject(XMLTokener x) throws RuntimeException {
		return (JsonObject) parse(x, false, null, false, 0);
	}

	/**
	 * Convert a well-formed (but not necessarily valid) XML string into a
	 * JSONObject using the JsonML transform. Each XML tag is represented as a
	 * JSONObject with a "tagName" property. If the tag has attributes, then the
	 * attributes will be in the JSONObject as properties. If the tag contains
	 * children, the object will have a "childNodes" property which will be an array
	 * of strings and JsonML JSONObjects.
	 * 
	 * Comments, prologs, DTDs, and
	 * 
	 * <pre>{@code &lt;[ [ ]]>}</pre>
	 * 
	 * are ignored.
	 * 
	 * @param x           An XMLTokener of the XML source text.
	 * @param keepStrings If true, then values will not be coerced into boolean or
	 *                    numeric values and will instead be left as strings
	 * @return A JSONObject containing the structured data from the XML string.
	 * @throws RuntimeException Thrown on error converting to a JSONObject
	 */
	public static JsonObject toJSONObject(XMLTokener x, boolean keepStrings) throws RuntimeException {
		return (JsonObject) parse(x, false, null, keepStrings, 0);
	}

	/**
	 * Convert a well-formed (but not necessarily valid) XML string into a
	 * JSONObject using the JsonML transform. Each XML tag is represented as a
	 * JSONObject with a "tagName" property. If the tag has attributes, then the
	 * attributes will be in the JSONObject as properties. If the tag contains
	 * children, the object will have a "childNodes" property which will be an array
	 * of strings and JsonML JSONObjects.
	 * 
	 * Comments, prologs, DTDs, and
	 * 
	 * <pre>{@code &lt;[ [ ]]>}</pre>
	 * 
	 * are ignored.
	 * 
	 * @param x      An XMLTokener of the XML source text.
	 * @param config The parser configuration: JSONMLParserConfiguration.ORIGINAL is
	 *               the default behaviour; JSONMLParserConfiguration.KEEP_STRINGS
	 *               means values will not be coerced into boolean or numeric values
	 *               and will instead be left as strings
	 * @return A JSONObject containing the structured data from the XML string.
	 * @throws RuntimeException Thrown on error converting to a JSONObject
	 */
	public static JsonObject toJSONObject(XMLTokener x, ParserConfiguration config) throws RuntimeException {
		return (JsonObject) parse(x, false, null, config, 0);
	}

	/**
	 * Reverse the JSONML transformation, making an XML text from a JSONArray.
	 * 
	 * @param ja A JSONArray.
	 * @return An XML string.
	 * @throws RuntimeException Thrown on error converting to a string
	 */
	public static String toString(JsonArray ja) throws RuntimeException {
		int i;
		JsonObject jo;
		int length;
		Object object;
		StringBuilder sb = new StringBuilder();
		String tagName;

// Emit <tagName

		tagName = ja.get(0).toString();
		noSpace(tagName);
		tagName = escape(tagName);
		sb.append('<');
		sb.append(tagName);

		object = ja.opt(1);
		if (object instanceof JsonObject) {
			i = 2;
			jo = (JsonObject) object;

// Emit the attributes

			// Don't use the new entrySet API to maintain Android support
			for (final String key : jo.keySet()) {
				final Object value = jo.get(key);
				noSpace(key);
				if (value != null) {
					sb.append(' ');
					sb.append(escape(key));
					sb.append('=');
					sb.append('"');
					sb.append(escape(value.toString()));
					sb.append('"');
				}
			}
		} else {
			i = 1;
		}

// Emit content in body

		length = ja.length();
		if (i >= length) {
			sb.append('/');
			sb.append('>');
		} else {
			sb.append('>');
			do {
				object = ja.get(i);
				i += 1;
				if (object != null) {
					if (object instanceof String) {
						sb.append(escape(object.toString()));
					} else if (object instanceof JsonObject) {
						sb.append(toString((JsonObject) object));
					} else if (object instanceof JsonArray) {
						sb.append(toString((JsonArray) object));
					} else {
						sb.append(object.toString());
					}
				}
			} while (i < length);
			sb.append('<');
			sb.append('/');
			sb.append(tagName);
			sb.append('>');
		}
		return sb.toString();
	}

	/**
	 * Reverse the JSONML transformation, making an XML text from a JSONObject. The
	 * JSONObject must contain a "tagName" property. If it has children, then it
	 * must have a "childNodes" property containing an array of objects. The other
	 * properties are attributes with string values.
	 * 
	 * @param jo A JSONObject.
	 * @return An XML string.
	 * @throws RuntimeException Thrown on error converting to a string
	 */
	public static String toString(JsonObject jo) throws RuntimeException {
		StringBuilder sb = new StringBuilder();
		int i;
		JsonArray ja;
		int length;
		Object object;
		String tagName;
		Object value;

//Emit <tagName

		tagName = jo.getString("tagName");
		if (tagName == null) {
			return escape(jo.toString());
		}
		noSpace(tagName);
		tagName = escape(tagName);
		sb.append('<');
		sb.append(tagName);

//Emit the attributes

		// Don't use the new entrySet API to maintain Android support
		for (final String key : jo.keySet()) {
			if (!"tagName".equals(key) && !"childNodes".equals(key)) {
				noSpace(key);
				value = jo.get(key);
				if (value != null) {
					sb.append(' ');
					sb.append(escape(key));
					sb.append('=');
					sb.append('"');
					sb.append(escape(value.toString()));
					sb.append('"');
				}
			}
		}

//Emit content in body

		ja = jo.getJSONArray("childNodes");
		if (ja == null) {
			sb.append('/');
			sb.append('>');
		} else {
			sb.append('>');
			length = ja.length();
			for (i = 0; i < length; i += 1) {
				object = ja.get(i);
				if (object != null) {
					if (object instanceof String) {
						sb.append(escape(object.toString()));
					} else if (object instanceof JsonObject) {
						sb.append(toString((JsonObject) object));
					} else if (object instanceof JsonArray) {
						sb.append(toString((JsonArray) object));
					} else {
						sb.append(object.toString());
					}
				}
			}
			sb.append('<');
			sb.append('/');
			sb.append(tagName);
			sb.append('>');
		}
		return sb.toString();
	}

	public static String escape(String string) {
		StringBuilder sb = new StringBuilder(string.length());
		for (final int cp : codePointIterator(string)) {
			switch (cp) {
			case '&':
				sb.append("&amp;");
				break;
			case '<':
				sb.append("&lt;");
				break;
			case '>':
				sb.append("&gt;");
				break;
			case '"':
				sb.append("&quot;");
				break;
			case '\'':
				sb.append("&apos;");
				break;
			default:
				if (mustEscape(cp)) {
					sb.append("&#x");
					sb.append(Integer.toHexString(cp));
					sb.append(';');
				} else {
					sb.appendCodePoint(cp);
				}
			}
		}
		return sb.toString();
	}

	private static Iterable<Integer> codePointIterator(final String string) {
		return new Iterable<Integer>() {
			@Override
			public Iterator<Integer> iterator() {
				return new Iterator<Integer>() {
					private int nextIndex = 0;
					private int length = string.length();

					@Override
					public boolean hasNext() {
						return this.nextIndex < this.length;
					}

					@Override
					public Integer next() {
						int result = string.codePointAt(this.nextIndex);
						this.nextIndex += Character.charCount(result);
						return result;
					}

					@Override
					public void remove() {
						throw new UnsupportedOperationException();
					}
				};
			}
		};
	}

	public static String unescape(String string) {
		StringBuilder sb = new StringBuilder(string.length());
		for (int i = 0, length = string.length(); i < length; i++) {
			char c = string.charAt(i);
			if (c == '&') {
				final int semic = string.indexOf(';', i);
				if (semic > i) {
					final String entity = string.substring(i + 1, semic);
					sb.append(XMLTokener.unescapeEntity(entity));
					// skip past the entity we just parsed.
					i += entity.length() + 1;
				} else {
					// this shouldn't happen in most cases since the parser
					// errors on unclosed entries.
					sb.append(c);
				}
			} else {
				// not part of an entity
				sb.append(c);
			}
		}
		return sb.toString();
	}

	public static void noSpace(String string) throws RuntimeException {
		int i, length = string.length();
		if (length == 0) {
			throw new RuntimeException("Empty string.");
		}
		for (i = 0; i < length; i += 1) {
			if (Character.isWhitespace(string.charAt(i))) {
				throw new RuntimeException("'" + string + "' contains a space character.");
			}
		}
	}

	public static Object stringToValue(String string) {
		if ("".equals(string)) {
			return string;
		}
		if ("true".equalsIgnoreCase(string)) {
			return Boolean.TRUE;
		}
		if ("false".equalsIgnoreCase(string)) {
			return Boolean.FALSE;
		}
		if ("null".equalsIgnoreCase(string)) {
			return null;
		}
		char initial = string.charAt(0);
		if ((initial >= '0' && initial <= '9') || initial == '-') {
			try {
				if (string.indexOf('.') == -1)
					return Long.valueOf(string);
				return Double.valueOf(string);
			} catch (Exception ignore) {
			}
		}
		return string;
	}

	private static boolean mustEscape(int cp) {
		return (Character.isISOControl(cp) && cp != 0x9 && cp != 0xA && cp != 0xD) || !((cp >= 0x20 && cp <= 0xD7FF)
				|| (cp >= 0xE000 && cp <= 0xFFFD) || (cp >= 0x10000 && cp <= 0x10FFFF));
	}
}
